
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Text;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

using LongNet.Util;

namespace LongNet.Config
{
    /// <summary>
    /// An water image helper to generate or save icon/text water images.
    /// This class cannot be inherited.
    /// </summary>
    public static class WaterImage
    {
        private static LongNet.Util.LogManager.Logger logger = LongNet.Util.LogManager.GetCurrentClassLogger();

        #region WaterPosition

        /// <summary>
        /// The positions used in <see cref="WaterImage"/>.
        /// </summary>
        public enum WaterPosition : byte
        {
            /// <summary>
            /// Top-Left.
            /// </summary>
            TopLeft,

            /// <summary>
            /// Top-Center.
            /// </summary>
            TopCenter,

            /// <summary>
            /// Top-Right.
            /// </summary>
            TopRight,

            /// <summary>
            /// Middle-Left.
            /// </summary>
            MiddleLeft,

            /// <summary>
            /// Middle-Center.
            /// </summary>
            MiddleCenter,

            /// <summary>
            /// Middle-Right.
            /// </summary>
            MiddleRight,

            /// <summary>
            /// Bottom-Left.
            /// </summary>
            BottomLeft,

            /// <summary>
            /// Bottom-Center.
            /// </summary>
            BottomCenter,

            /// <summary>
            /// Bottom-Right.
            /// </summary>
            BottomRight
        }

        #endregion

        #region SaveIconWaterImage/GenerateIconWaterImage

        /// <summary>
        /// Saves a water image by the specified <see cref="WaterPosition"/> and use value 0.8 for opacity.
        /// </summary>
        /// <param name="originalImagePath">The path of the original image.This is also the path to save the water.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(string originalImagePath, string waterPath, WaterPosition position)
        {
            return SaveIconWaterImage(originalImagePath, waterPath, 0.8F, position, 100);
        }

        /// <summary>
        /// Saves a water image by the specified <see cref="WaterPosition"/> and use value 0.8 for opacity.
        /// </summary>
        /// <param name="originalImagePath">The path of the original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(string originalImagePath, string savePath, string waterPath, WaterPosition position)
        {
            return SaveIconWaterImage(originalImagePath, savePath, waterPath, 0.8F, position, 100);
        }

        /// <summary>
        /// Saves a water image by the specified <see cref="WaterPosition"/> and use value 0.8 for opacity.
        /// </summary>
        /// <param name="originalImageStream">The <see cref="Stream"/> of the original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(Stream originalImageStream, string savePath, string waterPath, WaterPosition position)
        {
            return SaveIconWaterImage(originalImageStream, savePath, waterPath, 0.8F, position, 100);
        }

        /// <summary>
        /// Saves a water image by the specified <see cref="WaterPosition"/> and use value 0.8 for opacity.
        /// </summary>
        /// <param name="originalImage">The original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(Image originalImage, string savePath, string waterPath, WaterPosition position)
        {
            return SaveIconWaterImage(originalImage, savePath, waterPath, 0.8F, position, 100);
        }


        /// <summary>
        /// Saves a water image by the <c>WaterPosition.BottomLeft</c> mode and specified opacity.
        /// </summary>
        /// <param name="originalImagePath">The path of the original image.This is also the path to save the water.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(string originalImagePath, string waterPath, float opacity)
        {
            return SaveIconWaterImage(originalImagePath, waterPath, opacity, WaterPosition.BottomLeft, 100);
        }

        /// <summary>
        /// Saves a water image by the <c>WaterPosition.BottomLeft</c> mode and specified opacity.
        /// </summary>
        /// <param name="originalImagePath">The path of the original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(string originalImagePath, string savePath, string waterPath, float opacity)
        {
            return SaveIconWaterImage(originalImagePath, savePath, waterPath, opacity, WaterPosition.BottomLeft, 100);
        }

        /// <summary>
        /// Saves a water image by the <c>WaterPosition.BottomLeft</c> mode and specified opacity.
        /// </summary>
        /// <param name="originalImageStream">The <see cref="Stream"/> of the original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(Stream originalImageStream, string savePath, string waterPath, float opacity)
        {
            return SaveIconWaterImage(originalImageStream, savePath, waterPath, opacity, WaterPosition.BottomLeft, 100);
        }

        /// <summary>
        /// Saves a water image by the <c>WaterPosition.BottomLeft</c> mode and specified opacity.
        /// </summary>
        /// <param name="originalImage">The original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(Image originalImage, string savePath, string waterPath, float opacity)
        {
            return SaveIconWaterImage(originalImage, savePath, waterPath, opacity, WaterPosition.BottomLeft, 100);
        }


        /// <summary>
        /// Saves a water image by the specified opacity and <see cref="WaterPosition"/>.
        /// </summary>
        /// <param name="originalImagePath">The path of the original image.This is also the path to save the water.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <param name="quality">The quality must in range [1, 100].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(string originalImagePath, string waterPath, float opacity, WaterPosition position, int quality)
        {
            return SaveIconWaterImage(originalImagePath, originalImagePath, waterPath, opacity, position, quality);
        }

        /// <summary>
        /// Saves a water image by the specified opacity and <see cref="WaterPosition"/>.
        /// </summary>
        /// <param name="originalImagePath">The path of the original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <param name="quality">The quality must in range [1, 100].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(string originalImagePath, string savePath, string waterPath, float opacity, WaterPosition position, int quality)
        {
            if (originalImagePath.IsNullOrTrimedEmpty())
                throw new ArgumentException("The originalImagePath is null or empty.", "originalImagePath");
            if (savePath.IsNullOrTrimedEmpty())
                throw new ArgumentException("The savePath is null or empty.", "savePath");

            Image waterImage = null;
            Image originalImage = null;

            try
            {
                originalImage = Image.FromFile(originalImagePath);
                waterImage = GenerateIconWaterImage(originalImage, waterPath, opacity, position);
                // disposes the originalImage first
                // otherwise if the originalImagePath and savePath are the same path, it will fail to save
                originalImage.Dispose();
                originalImage = null;

                var ici = ImageUtils.GetImageCodecInfo(savePath);
                if (ici == null)
                    waterImage.Save(savePath, ImageUtils.GetImageFormat(savePath));
                else
                    waterImage.Save(savePath, ici, ImageUtils.GetQualityEncoderParameters(quality));

                return true;
            }
            catch (Exception ex)
            {
                if (logger.IsErrorEnabled)
                    logger.Error(ex, "Fail to save water image : {0}", savePath);

                return false;
            }
            finally
            {
                if (waterImage != null)
                    waterImage.Dispose();
                if (originalImage != null)
                    originalImage.Dispose();
            }
        }

        /// <summary>
        /// Saves a water image by the specified opacity and <see cref="WaterPosition"/>.
        /// </summary>
        /// <param name="originalImageStream">The <see cref="Stream"/> of the original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <param name="quality">The quality must in range [1, 100].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(Stream originalImageStream, string savePath, string waterPath, float opacity, WaterPosition position, int quality)
        {
            if (originalImageStream == null)
                throw new ArgumentNullException("originalImageStream", "The originalImageStream is null.");
            if (savePath.IsNullOrTrimedEmpty())
                throw new ArgumentException("The savePath is null or empty.", "savePath");

            Image waterImage = null;

            try
            {
                waterImage = GenerateIconWaterImage(originalImageStream, waterPath, opacity, position);

                var ici = ImageUtils.GetImageCodecInfo(savePath);
                if (ici == null)
                    waterImage.Save(savePath, ImageUtils.GetImageFormat(savePath));
                else
                    waterImage.Save(savePath, ici, ImageUtils.GetQualityEncoderParameters(quality));

                return true;
            }
            catch (Exception ex)
            {
                if (logger.IsErrorEnabled)
                    logger.Error(ex, "Fail to save water image : {0}", savePath);

                return false;
            }
            finally
            {
                if (waterImage != null)
                    waterImage.Dispose();
            }
        }

        /// <summary>
        /// Saves a water image by the specified opacity and <see cref="WaterPosition"/>.
        /// </summary>
        /// <param name="originalImage">The original image.</param>
        /// <param name="savePath">The path to save the water made from original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <param name="quality">The quality must in range [1, 100].</param>
        /// <returns>True if the save was succesful.</returns>
        public static bool SaveIconWaterImage(Image originalImage, string savePath, string waterPath, float opacity, WaterPosition position, int quality)
        {
            if (originalImage == null)
                throw new ArgumentNullException("originalImage", "The originalImage is null.");
            if (savePath.IsNullOrTrimedEmpty())
                throw new ArgumentException("The savePath is null or empty.", "savePath");

            Image waterImage = null;

            try
            {
                waterImage = GenerateIconWaterImage(originalImage, waterPath, opacity, position);

                var ici = ImageUtils.GetImageCodecInfo(savePath);
                if (ici == null)
                    waterImage.Save(savePath, ImageUtils.GetImageFormat(savePath));
                else
                    waterImage.Save(savePath, ici, ImageUtils.GetQualityEncoderParameters(quality));

                return true;
            }
            catch (Exception ex)
            {
                if (logger.IsErrorEnabled)
                    logger.Error(ex, "Fail to save water image : {0}", savePath);

                return false;
            }
            finally
            {
                if (waterImage != null)
                    waterImage.Dispose();
            }
        }


        /// <summary>
        /// Generates a water image by the specified opacity and <see cref="WaterPosition"/>.
        /// </summary>
        /// <param name="originalImageStream">The <see cref="Stream"/> of the original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <returns>The water image.</returns>
        public static Image GenerateIconWaterImage(Stream originalImageStream, string waterPath, float opacity, WaterPosition position)
        {
            if (originalImageStream == null)
                throw new ArgumentNullException("originalImageStream", "The originalImageStream is null.");

            using (Image image = Image.FromStream(originalImageStream))
            {
                Image waterImage = GenerateIconWaterImage(image, waterPath, opacity, position);
                image.Dispose();
                return waterImage;
            }
        }

        /// <summary>
        /// Generates a water image by the specified opacity and <see cref="WaterPosition"/>.
        /// </summary>
        /// <param name="originalImage">The original image.</param>
        /// <param name="waterPath">The path of the water image.</param>
        /// <param name="opacity">The opacity of the water image, the value is in range [0,1].</param>
        /// <param name="position">The <see cref="WaterPosition"/> to make water image by.</param>
        /// <returns>The water image.</returns>
        public static Image GenerateIconWaterImage(Image originalImage, string waterPath, float opacity, WaterPosition position)
        {
            if (originalImage == null)
                throw new ArgumentNullException("originalImage", "The originalImage is null.");
            if (waterPath.IsNullOrTrimedEmpty())
                throw new ArgumentException("The waterPath is null or empty.", "waterPath");
            if (File.Exists(waterPath) == false)
                throw new FileNotFoundException("The water image does not exist.");

            int x = 0;
            int y = 0;
            int w = originalImage.Width;
            int h = originalImage.Height;
            Image waterImage = null;

            using (var image = originalImage.Clone<Image>())
            {
                waterImage = new Bitmap(image);
                using (var graphics = Graphics.FromImage(waterImage))
                {
                    using (var water = Image.FromFile(waterPath))
                    {
                        #region set edge and position

                        int edgeX = (int)(w * 0.01F);
                        int edgeY = (int)(h * 0.01F);

                        switch (position)
                        {
                            case WaterPosition.TopLeft:
                                x = edgeX;
                                y = edgeY;
                                break;
                            case WaterPosition.TopCenter:
                                x = (int)((w - water.Width) * 0.5F);
                                y = edgeY;
                                break;
                            case WaterPosition.TopRight:
                                x = w - water.Width - edgeX;
                                y = edgeY;
                                break;

                            case WaterPosition.MiddleLeft:
                                x = edgeX;
                                y = (int)((h - water.Height) * 0.5F);
                                break;
                            case WaterPosition.MiddleCenter:
                                x = (int)((w - water.Width) * 0.5F);
                                y = (int)((h - water.Height) * 0.5F);
                                break;
                            case WaterPosition.MiddleRight:
                                x = w - water.Width - edgeX;
                                y = (int)((h - water.Height) * 0.5F);
                                break;

                            case WaterPosition.BottomLeft:
                                x = edgeX;
                                y = h - water.Height - edgeY;
                                break;
                            case WaterPosition.BottomCenter:
                                x = (int)((w - water.Width) * 0.5F);
                                y = h - water.Height - edgeY;
                                break;
                            case WaterPosition.BottomRight:
                                x = w - water.Width - edgeX;
                                y = h - water.Height - edgeY;
                                break;
                            default:
                                throw new NotSupportedException("Not supported WaterPosition.");
                        }

                        #endregion

                        using (var attribute = new ImageAttributes())
                        {
                            #region set opacity
                            //设定透明色
                            //attribute.SetColorKey(Color.White, Color.White, ColorAdjustType.Bitmap);

                            ColorMap colorMap = new ColorMap { OldColor = Color.FromArgb(255, 0, 255, 0), NewColor = Color.FromArgb(0, 0, 0, 0) };
                            attribute.SetRemapTable(new ColorMap[] { colorMap }, ColorAdjustType.Bitmap);

                            if (opacity < 0 || opacity > 1.0F)
                                opacity = 0.5F;

                            float[][] colorMatrixElements = {
												new float[] {1.0f,  0.0f,  0.0f,  0.0f, 0.0f},
												new float[] {0.0f,  1.0f,  0.0f,  0.0f, 0.0f},
												new float[] {0.0f,  0.0f,  1.0f,  0.0f, 0.0f},
												new float[] {0.0f,  0.0f,  0.0f,  opacity, 0.0f},
												new float[] {0.0f,  0.0f,  0.0f,  0.0f, 1.0f}
											};

                            ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
                            attribute.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

                            #endregion

                            // 画水印图片
                            var destRect = new Rectangle(x, y, water.Width, water.Height);
                            graphics.DrawImage(water, destRect, 0, 0, water.Width, water.Height, GraphicsUnit.Pixel, attribute);

                            attribute.Dispose();
                            graphics.Dispose();
                            image.Dispose();

                            return waterImage;
                        }
                    }
                }
            }
        }

        #endregion

        #region SaveTextWaterImage/GenerateTextWaterImage

        // 未完成

        #endregion
    }
}
